package sceneInfo;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.Math;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

import rcscene.Action;
import rcscene.ObjectWeights;

import weka.core.Attribute;
import weka.core.FastVector;
import weka.core.Instance;
import weka.core.Instances;

public class FuzzySceneLib extends SceneLib {

	public static final long serialVersionUID = 2L;
	/**
	 * number of different object types, thus number of different histograms in fuzzy scenes
	 */
	public static final int NUMBER_OF_OBJECTTYPES = 7;
	/**
	 * names of object types
	 */
	public static final String OBJECTTYPENAME [] = {"Ball", "Teammates", "Oppos", "Unknown", "Goal", "Flags","Lines"};

	private ArrayList<FuzzyScene> fuzzyScenes; //-- might want to put the fuzzyscenes in here.

	private FuzzyFramework framework; //reference to the fuzzy logic framework with its parameters


	private ObjectWeights objWeights; // object weights

	/**
	 * default constructor : creates an empty fuzzySceneLib
	 */
	public FuzzySceneLib (){
		super();
		fuzzyScenes = new ArrayList<FuzzyScene>(); // this stays null as long as the fuzzy scenes have not been computed
		framework = null; // no framework for now
	}

	/**
	 * Constructor : creates a fuzzySceneLib an populates it by parsing the logfile (name given as argument)
	 * @param logfile : the logfile name
	 */
	public FuzzySceneLib (String logfile){
		super();
		parseLog(logfile); // populate scenes from log file
		fuzzyScenes = new ArrayList<FuzzyScene>(); // this stays null as long as the fuzzy scenes have not been computed
		framework = null; // no framework for now
		objWeights = null;
	}

	public FuzzySceneLib(SceneLib crisplib, FuzzyFramework frame){
			this(crisplib, frame, null);
	}

	/**
	 * builds a fuzzy scene lib from an existing sceneLib object. Shallow copies the scenes and adds the fuzzy ones.
	 * @param crisplib
	 * @param frame
	 */
	public FuzzySceneLib(SceneLib crisplib, FuzzyFramework frame, ObjectWeights ow){

		objWeights = ow;
		orderedScenes = crisplib.getSceneList(); // SHALLOW COPY
		fuzzyScenes = new ArrayList<FuzzyScene>();

		discretizeThis(frame);
	}




	/**
	 * creates the fuzzy scenes from the existing scenes and the fuzzy framework provided
	 * @param frmwk
	 */
	public void discretizeThis(FuzzyFramework frmwk){
		setFramework(frmwk);
		FuzzyScene newfuzzy;

		// create all the fuzzy scenes from the crisp scenes
		//System.out.println("about to start creating fuzzy scenes\n");
		int count=0;
		Iterator<Scene> it = orderedScenes.iterator();

		while (it.hasNext())
		{
			//System.out.print("i");
			Scene thisScene = it.next();
			newfuzzy = new FuzzyScene(thisScene, framework, objWeights);
			fuzzyScenes.add(newfuzzy);
			count++;
		}
		//System.out.println(count);
		//System.out.println(" created");
	}

	/**
	 *  Get a weka dataset just from the kicks, dashes or turns, and make one action parameter the
	 * @param action
	 * @param parameter
	 * @return
	 */
	public Instances getDataset(int action, int parameter){
		//action is kick, dash, turn
		// parameter is power or angle
		return null;
		//TODO : make only one method that goes through the scenes and creates weka instances,
		//we need 5 separate instances objects, one for kick-dash-turn determination, 2 for kick params, then 1 for each of dash - turn parameters
	}

	/**
	 * can only be called if the scenes have been already fuzzyfied
	 * @return
	 */
	public Instances toWekaDataset(){

		int datasize = framework.getGridSize()* NUMBER_OF_OBJECTTYPES;

	    String RelationName = "RobocupData"+ fuzzyScenes.get(0).getTeamName(); //team name of fist scene

		// create the attribute list :
		FastVector attributes = new FastVector(datasize +1 ); //datasize histogram values, + 1 for the action

		//create all the numeric attributes (histogram bins)
		for (int j =0; j<datasize; j++){
			  attributes.addElement(new Attribute(myName(j)));//attribute name made from angle / distance / objecttype
			  //"HBin"+String.valueOf(j))); // just give it a name : by default it is numeric
		}
		//add last attribute
		FastVector actions = new FastVector(7);
		for  (String act : Action.actions)
			actions.addElement(act);

		attributes.addElement(new Attribute("action", actions)); // class attribute

		int capacity = fuzzyScenes.size();

		Instances toReturn = new Instances(RelationName, attributes, capacity);

		toReturn.setClassIndex(datasize); // the last one is the class

		/* adding all scenes to the empty instances list */
		for (FuzzyScene fs : fuzzyScenes){

			toReturn.add(fs.toWekaInstance());
		}

		return toReturn;
	}

	/**
	 * can only be called if the scenes have been already fuzzyfied
	 * @return
	 */
	public Instances toCompleteWekaDataset(){

		int datasize = framework.getGridSize()* NUMBER_OF_OBJECTTYPES;

	    String RelationName = "RobocupData"+ fuzzyScenes.get(0).getTeamName(); //team name of fist scene

		// create the attribute list :
		FastVector attributes = new FastVector(datasize +3 ); //datasize histogram values, + 1 for the action + 2 for action parameters

		//create all the numeric attributes (histogram bins)
		for (int j =0; j<datasize; j++){
			  attributes.addElement(new Attribute(myName(j)));//attribute name made from angle / distance / objecttype
			  //"HBin"+String.valueOf(j))); // just give it a name : by default it is numeric
		}
		//add last attribute
		FastVector actions = new FastVector(7);
		for  (String act : Action.actions)
			actions.addElement(act);

		attributes.addElement(new Attribute("action", actions)); // action {kick, dash, turn}
		attributes.addElement(new Attribute("actionPower")); // action power (numeric)
		attributes.addElement(new Attribute("actionAngle")); // action angle (numeric)

		int capacity = fuzzyScenes.size();

		Instances toReturn = new Instances(RelationName, attributes, capacity);

		// NO class index is set : the class index will depend on what we're trying to do.
		//toReturn.setClassIndex(datasize); // the last one is the class

		/* adding all scenes to the empty instances list */
		for (FuzzyScene fs : fuzzyScenes){

			toReturn.add(fs.toWekaInstance());
		}

		return toReturn;
	}

	/**
	 * accesses array of fuzzy scenes
	 * @return
	 */
	public ArrayList<FuzzyScene> getFuzzySceneList(){
		return fuzzyScenes;
	}

	/** changes the fuzzy framework
	 * as a side effect, also resets the fuzzy scene list
	 *
	 */
	public void setFramework(FuzzyFramework framework) {
		fuzzyScenes.clear();
		this.framework = framework;
	}

	/**
	 *  gets thge fuzzy framework associated to this FuzzySceneLib
	 * @return
	 */
	public FuzzyFramework getFramework() {
		return framework;
	}

	/**
	 *  populate the scene library from a log file.
	 *  Note: doesn't delete existing scenes.
	 *
	 * @param logFileName
	 */
	public void parseLog(String logFileName) {

		int linecount = 0;
		String my_team;


		StringTokenizer m_tokenizer;

		try
		{
			BufferedReader in = new BufferedReader(new FileReader(logFileName));
            StringBuffer test = new StringBuffer(logFileName.substring(logFileName.lastIndexOf("/")+1));
			my_team = new String(test);
			try {
				my_team = new String(test.substring(0,test.lastIndexOf("_")));		// default team name is the filename of the log
			}
			catch (Exception e)
			{
			}

			System.out.println("Using teamname " + my_team);

			String str;
			String mytoken;

			Scene currentScene = new Scene();
			currentScene.setIdentString("0");		// the time stamp of this scene
			//////////////////////////////////////////
            currentScene.setTeamName(my_team);
            ////////////////////////////////////////////
            int currentTime = 0;					// easy integer representation of this scene's time


			System.out.println("Reading log data...");
			while ((str = in.readLine()) != null)
			{
				linecount++;		// just a counter for reporting purposes

				// step 1: tokenize first word

				str.trim();
				m_tokenizer = new StringTokenizer(str, "() ", true);
				if (m_tokenizer.hasMoreTokens())
				{
					m_tokenizer.nextToken(); // '('
					mytoken = m_tokenizer.nextToken(); // 'sense_body', etc.

					// step 2: based on word, pass to other functions as necessary
					//         right now we really only care about "see" and "sense_body" and the player actions

					if (mytoken.equals("init"))
					{
						m_tokenizer.nextToken();	// eat up the space
							String myTeamName = "\"" + m_tokenizer.nextToken() + "\"";
                            System.out.println("My team is " + myTeamName);
                            // Added by Deryck Velasquez 11/26/04:
                            // Store the team name of the observer.
                            currentScene.setTeamName(myTeamName);
                            my_team = myTeamName;

					}
					if (mytoken.equals("sense_body"))
					{
						SenseBodyInfo logBody = new SenseBodyInfo(str);

						// ok, now we know about our own velocity and stamina and stuff...
						// store somewhere in scene? currently no provision
						// should add later...

						if ((int)logBody.time == currentTime)
						{
							// attach the contents of logBody to the currentScene
						}
						else		// we've got a new time point.. so we must be done with the old one
						{
							placeScene(currentScene);

							currentScene = new Scene();
//							////////////////////////////////////////
							currentScene.setTeamName(my_team);
//							//////////////////////////////////////////
							currentScene.setIdentString("" + ((int)logBody.time));		// the time stamp of this scene

							currentTime = (int) logBody.time;

						}


					}
					else if (mytoken.equals("see"))
					{
						VisualInfo logSee = new VisualInfo(str, my_team, true);

						try{
							logSee.parse();
						}catch (Exception e){
							System.out.println("exception caught !");

							continue;
						}


						if(logSee.getTime() != 0)
						{
							if (logSee.getTime() == currentTime)
							{
								currentScene.setVision(logSee);						// what to do if one timepoint's data spans over two see's?
							}
							else		// we've got a new time point.. so we must be done with the old one
							{
								placeScene(currentScene); // store scene in library
								currentScene = new Scene();
								currentScene.setIdentString("" + ((int)logSee.getTime()));		// the time stamp of this scene
								currentScene.setTeamName(my_team);
								currentTime = (int) logSee.getTime();
								currentScene.setVision(logSee);
							}
						}

					}

					// actions
					// hard-coding it so that only 2 actions are stored
					else if (mytoken.equals("dash"))
					{
						Action newDash = new Action(Action.ACTION_DASH);
						m_tokenizer.nextToken(); // the ' '

						// also set angle, power and stuff!
						newDash.setActionPower(Float.valueOf(m_tokenizer.nextToken()).floatValue());

						m_tokenizer.nextToken(); // the ')'

						currentScene.addAction(newDash);

					}
					else if (mytoken.equals("turn"))
					{
						Action newTurn = new Action(Action.ACTION_TURN);
						m_tokenizer.nextToken(); // the ' '

						// also set angle, power and stuff!
						newTurn.setActionDirection(Float.valueOf(m_tokenizer.nextToken()).floatValue());

						m_tokenizer.nextToken(); // the ')'

						currentScene.addAction(newTurn);

					}
					else if (mytoken.equals("turn_neck"))
					{
						Action newTurn = new Action(Action.ACTION_TURNNECK);
						m_tokenizer.nextToken(); // the ' '

						// also set angle, power and stuff!
						newTurn.setActionDirection(Float.valueOf(m_tokenizer.nextToken()).floatValue());

						m_tokenizer.nextToken(); // the ')'

						currentScene.addAction(newTurn);

					}
					else if (mytoken.equals("kick"))
					{
						Action newKick = new Action(Action.ACTION_KICK);
						m_tokenizer.nextToken(); // the ' '

						// also set angle, power and stuff!
						newKick.setActionPower(Float.valueOf(m_tokenizer.nextToken()).floatValue());

						m_tokenizer.nextToken(); // the ' '

						newKick.setActionDirection(Float.valueOf(m_tokenizer.nextToken()).floatValue());

						m_tokenizer.nextToken(); // the ')'

						currentScene.addAction(newKick);

					}
					else if (mytoken.equals("catch"))
					{
						currentScene.addAction(new Action(Action.ACTION_CATCH));

					}

					// otherwise ignore it, we don't care right now
				}
			}
			// is currentSlice = 6000 now, and not added?
			placeScene(currentScene);

			in.close();

			// finished reading file and building objects...
			//pruneScenes();
			// pruning is already done in "placeScene"

			//guessTargets();

			System.out.println("Done (read " + linecount + " lines).\n");
		}
		catch (Exception e) {

		}
	}

	/**
	 * output scenes to a file (specified by argument)
	 * @param sceneFilename file name for the output, without extention (two files will in fact be output, one readable and one binary)
	 */
	public void writeScenes(String sceneFileName)
	{
		try
		{
			FileOutputStream fileOut = new FileOutputStream(sceneFileName + ".text");
			OutputStreamWriter outWriter =
				new OutputStreamWriter(fileOut, "US-ASCII");
			// throws exception
			PrintWriter out = new PrintWriter(outWriter); // throws exception

			System.out.println("Writing Scene Pictures file to " + sceneFileName + ".text");
			out.println("% Robocup Scene pictures generated by FuzzySceneLib.java");
			int numRows = framework.distnumber;
			int numCols = framework.alphanumber;

			out.println("% Using rowsize=" + numRows + ", colsize=" + numCols);
			// iterate through objects here
			Iterator<FuzzyScene> it = fuzzyScenes.iterator();
			int [] avg = new int[numRows*numCols];
			for (int j = 0; j<numRows*numCols; j++){
				  avg[j]=0;
				  }
			while (it.hasNext())
			{
				FuzzyScene thisScene = it.next();

				out.println("Scene " + thisScene.getFuzzyVInfo().getTime());
				out.println("Team " + thisScene.getTeamName());
				//try {
				  out.println("# of Ball/Teammates/Oppos/Unknown/Goal/Flags/Lines = ");
				  int [] numbers = thisScene.getFuzzyVInfo().BigHistogram;//  .getObjectCount();


				  for (int i = 0; i<7; i++){
					  for (int j = 0; j<numRows*numCols; j++){
						  out.print(numbers[i*(numRows*numCols)+j]+ " ");
						  avg[j] += numbers[i*(numRows*numCols)+j];
						  }
					out.println();
				  }

				//} catch(NullPointerException e) {}
				// out.println("  table:\n" + thisScene.outputTable());
				out.println("  action:" + thisScene.getActions());
				Iterator actionIt = thisScene.getActions().iterator();
				while(actionIt.hasNext()) {
				  Action action = (Action) actionIt.next();
				  out.println("  actionDirection: " + action.getActionDirection() + "  actionPower: " + action.getActionPower());
				}
				out.println();
			}

			// end of human-readable format...
			out.close();
			outWriter.close(); // throws exception
			fileOut.close();

			System.out.println("Writing Scene Store file to " + sceneFileName + ".scene");
			System.out.println("average cell fill: ");
			float av;
			for (int j = 0; j<numRows*numCols; j++){
				  av = avg[j]/fuzzyScenes.size();
				System.out.print(av+" ");
				  }
			fileOut = new FileOutputStream(sceneFileName + ".scene");


			//TODO : we're not necessarily interested in outputting the sceneLib object
			//Find what else. Maybe the ARFF
			ObjectOutputStream oos = new ObjectOutputStream(fileOut);
				oos.writeObject(new Integer(numRows));
			    oos.writeObject(new Integer(numCols));
				oos.writeObject(this);
				oos.close();
			fileOut.close();

		}
		catch (FileNotFoundException e)
		{

			e.printStackTrace();
		}
		catch (UnsupportedEncodingException e)
		{

			e.printStackTrace();
		}
		catch (IOException e)
		{

			e.printStackTrace();
		}

		System.out.println("Wrote " + fuzzyScenes.size() + " scenes.");

	}

	/**
	 *  output the SceneLib as an ARFF dataset file.
	 *  note: the classification problem represented by this arff file is just to classify a scene as "kick, dash, turn".
	 *  The actual parameters are not calculated.
	 * @param sceneFileName
	 */
	public void writeScenesArff(String sceneFileName) {
		try
		{
			FileOutputStream fileOut = new FileOutputStream(sceneFileName + ".arff");
			OutputStreamWriter outWriter =
				new OutputStreamWriter(fileOut, "US-ASCII");
			// throws exception
			PrintWriter out = new PrintWriter(outWriter); // throws exception

			System.out.println("Writing Arff file to " + sceneFileName + ".arff");
			out.println("% Robocup Scene pictures generated by LogToFuzzyScenes.java");
			int numRows = framework.distnumber;
			int numCols = framework.alphanumber;

			out.println("% Using rowsize=" + numRows + ", colsize=" + numCols);

			out.println("@RELATION krislet");
			//listing the attributes of each scene

			//this is the right order in the big histogram according to the membership function of FuzzyFramework
			for (int ty = 0; ty<7; ty++){
				for (int ro= 0; ro<numRows; ro++) {
					for (int co= 0; co<numCols; co++) {

						out.println("@ATTRIBUTE a"+ myName(ro,co,ty) +" NUMERIC");
					}
				}
			}
			   out.println("@ATTRIBUTE class        {kick, dash, turn}");
			   out.println("@DATA");
			// iterate through objects here
			Iterator<FuzzyScene> it = fuzzyScenes.iterator();
			int [] avg = new int[numRows*numCols];
			for (int j = 0; j<numRows*numCols; j++){
				  avg[j]=0;
				  }
			while (it.hasNext())
			{
				FuzzyScene thisScene = it.next();

				int [] numbers = thisScene.getFuzzyVInfo().BigHistogram;


				  for (int i = 0; i<7; i++){
					  for (int j = 0; j<numRows*numCols; j++){
						  out.print(numbers[i*(numRows*numCols)+j]+ ",");
						  avg[j] += numbers[i*(numRows*numCols)+j];
						  }
				  }
				out.println(thisScene.getActions().get(0).toString());

			}

			// end of human-readable format...
			out.close();
			outWriter.close(); // throws exception
			fileOut.close();
		}
		catch (Exception e) {e.printStackTrace();}

	}

	/**
	 * This method returns an attribute name for each attribute in the feature vector of a scene representation
	 * @param i the row (distance)
	 * @param j the column (angle)
	 * @param k the object type (ball, teammate, etc.)
	 * @return a string including all three information
	 */
	private String myName(int i, int j, int k){

		String myRow = Integer.toString(i);
		String myCol = Integer.toString(j);
		String myObj [] = {"Ball", "Teammates", "Oppos", "Unknown", "Goal", "Flags","Lines"};

		return myObj[k]+"_dist"+myRow+"_angle"+myCol;
	}

	/**
	 * This method returns an attribute name for each attribute in the feature vector of a scene representation
	 * Figures out row, column, objtype from framework.
	 * @param i the histogram bin.
	 * @return a string including all three information row, col, objtype
	 */
	private String myName(int i){

		int objtype = i/(framework.alphanumber*framework.distnumber);
		int row = (i%(framework.alphanumber*framework.distnumber))/framework.alphanumber;
		int col = (i%(framework.alphanumber*framework.distnumber))%framework.alphanumber;
		//System.out.println(i+ "row " +row+ "col "+col+ "ot"+objtype);
		String strRow = Integer.toString(row);
		String strCol = Integer.toString(col);


		return OBJECTTYPENAME[objtype]+"_dist"+strRow+"_angle"+strCol;
	}

	/**
	 * Quantify the confusion induced by the discretization.
	 * Count the equal scenes (in terms of visual info) and see whether the associated actions are similar or not.
	 * If similar (same aciton type and parameter difference is less than action std deviation), that's ok.
	 * Otherwise we have a situation that makes learning impossible because two scenes are indistinguishable
	 * and have different associated actions.
	 *
	 */
	public float evaluateConfusion()
	{

		return evaluateConfusion(true, true);
	}


	/**
	 * Quantify the confusion induced by the discretization.
	 * Count the equal scenes (in terms of visual info) and see whether the associated actions are similar or not.
	 * If similar (same aciton type and parameter difference is less than action std deviation), that's ok.
	 * Otherwise we have a situation that makes learning impossible because two scenes are indistinguishable
	 * and have different associated actions.
	 *
	 * param: checkpower checkdirection
	 *
	 */
	public float evaluateConfusion(boolean checkpower, boolean checkdirection)
	{


		int totalScenes = 0;
		int equalCount = 0;
		int localCount = 0;
		int matches= 0; //my counter for counting matches
		int falsepositives =0; //mycounter for counting equal but mismatched
		int localfalsepositives;

		// iterate through scenes

		FuzzyScene thisScene, thatScene;
		Action A1, A2;
		ArrayList<Integer> scenescompared = new ArrayList<Integer>();
		boolean flagcompared = false;

			// loop through all scenes, count all similar scenes with same, different actions
			//using array index so that we can iterate on the full list for scene1 , and on the remaining list [scenes *after* scene1] for scene2.
			for (int i1 =0; i1<fuzzyScenes.size();i1++)
			{
				//edited by  bruna, to skip compared
				flagcompared=false;
				for(int k=0; k<scenescompared.size();k++){
					if(i1==(int)scenescompared.get(k)){
						flagcompared=true;
						//System.out.println("scene equal: " + (int)scenescompared.get(k));
						scenescompared.remove(k);

						break;
					}
				}

				//System.out.println("compared?"+ flagcompared);
				if(flagcompared) continue; //still bruna
				//System.out.println("compared?"+ flagcompared);
				thisScene = fuzzyScenes.get(i1); // first scene to compare
				/*if (i1==12){
					for (int kk:thisScene.getFuzzyVInfo().BigHistogram)

					System.out.print(kk+",");
				}*/



				localCount =0;
				localfalsepositives=0;

					for (int i2 =i1+1; i2<getFuzzySceneList().size(); i2++){
						thatScene = fuzzyScenes.get(i2);
						totalScenes++;
							if (thisScene.getFuzzyVInfo().equals(thatScene.getFuzzyVInfo())){
								localCount++;
								A1 = thisScene.getActions().get(0);
								A2 = thatScene.getActions().get(0);

								if (A1.getAction()==A2.getAction() ){

									if(checkpower || checkdirection){


										switch (A1.getAction()){
										case Action.ACTION_KICK:
											if (checkdirection && Math.abs(A1.getActionDirection()-A2.getActionDirection())>Action.KICK_SD){
												falsepositives++;
											}
											else {
												matches++;
												scenescompared.add(i2);}

										case Action.ACTION_DASH:
											if (checkpower && Math.abs(A1.getActionPower()-A2.getActionPower())>Action.DASH_SD){
												falsepositives++;
											}
											else {
												matches++;
												scenescompared.add(i2);}
										case Action.ACTION_TURN:
											if (checkdirection && Math.abs(A1.getActionDirection()-A2.getActionDirection())>Action.TURN_SD){
												falsepositives++;
											}
											else {
												matches++;
												scenescompared.add(i2);}
										default:
											matches++;
											scenescompared.add(i2);
										}
									}
								}
								else
									falsepositives++;	//it's  a mismatch



							}

						}




			}


		//==== printing everything to the given outputfile =========================
		System.out.println("Scenes compared:" + totalScenes);
		System.out.println("Action matches:" + matches);
		System.out.println("Action mismatches:" + falsepositives);

		//System.out.println();//blank

		//System.out.println("Comparison done.");
		// return the ratio of indistinguishable scenes
		/*float toreturn = (float)(10*falsepositives)/(float)(matches+1);//a pretty random metric
		System.out.println("returning"+  toreturn);*/
		/// we return the ratio of "equal scenes with different actions" to the total number of equal scenes. (number of equal pairs)
		return (float)falsepositives/(float)(falsepositives + matches);
	}

	/** set the object weights for this sceneLib*/
		public void setObjectWeights(ObjectWeights objWeights) {
			this.objWeights = objWeights;

		}

		public ObjectWeights getObjectWeights() {
			// TODO Auto-generated method stub
			return objWeights;
	}

}
